﻿using System;
using System.Configuration;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;

namespace Black.Minecraft.Editor.Behaviors
{
    /// <summary>
    /// Behavior permettant de binder une command sur le tap sur un élément
    /// </summary>
    public class TapToCommandBehavior : Behavior<UIElement>
    {
        #region Propriétés de dépendances

        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(TapToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(TapToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty PassTouchArgsProperty = DependencyProperty.Register("PassTouchArgs", typeof(bool), typeof(TapToCommandBehavior), new PropertyMetadata(false));
        public static readonly DependencyProperty TouchDownStoryboardProperty = DependencyProperty.Register("TouchDownStoryboard", typeof(Storyboard), typeof(TapToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty TouchUpStoryboardProperty = DependencyProperty.Register("TouchUpStoryboard", typeof(Storyboard), typeof(TapToCommandBehavior), new PropertyMetadata(null));

        #endregion

        #region Propriétés

        /// <summary>
        /// Obtient ou définit la commande a éxécuter sur l'événement
        /// </summary>
        public ICommand Command
        {
            get
            {
                return ((ICommand)this.GetValue(TapToCommandBehavior.CommandProperty));
            }
            set
            {
                this.SetValue(TapToCommandBehavior.CommandProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le paramètre de la commande a éxécuter sur l'événement
        /// </summary>
        public object CommandParameter
        {
            get
            {
                return ((object)this.GetValue(TapToCommandBehavior.CommandParameterProperty));
            }
            set
            {
                this.SetValue(TapToCommandBehavior.CommandParameterProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit une propriété indiquant si les paramètres du touch doivent être passés à la commande.
        /// </summary>
        public bool PassTouchArgs
        {
            get
            {
                return ((bool)this.GetValue(TapToCommandBehavior.PassTouchArgsProperty));
            }
            set
            {
                this.SetValue(TapToCommandBehavior.PassTouchArgsProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le storyboard à éxécuter au touch down sur le contrôle
        /// </summary>
        public Storyboard TouchDownStoryboard
        {
            get
            {
                return ((Storyboard)this.GetValue(TapToCommandBehavior.TouchDownStoryboardProperty));
            }
            set
            {
                this.SetValue(TapToCommandBehavior.TouchDownStoryboardProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le storyboard à éxécuter au touch up sur le contrôle
        /// </summary>
        public Storyboard TouchUpStoryboard
        {
            get
            {
                return ((Storyboard)this.GetValue(TapToCommandBehavior.TouchUpStoryboardProperty));
            }
            set
            {
                this.SetValue(TapToCommandBehavior.TouchUpStoryboardProperty, value);
            }
        }

        #endregion

        #region Variables d'instances

        private bool _isTouching;

        private readonly double _maxLength;

        private readonly double _maxDuration;

        private DateTime _mouseDownTime = DateTime.MinValue;

        private TouchPoint _startTouch;

        private DateTime _touchDownTime = DateTime.MinValue;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public TapToCommandBehavior()
            : base()
        {
            string maxLengthSetting = ConfigurationManager.AppSettings["TapMaxLength"];
            this._maxLength = 15;
            if (!string.IsNullOrEmpty(maxLengthSetting))
                double.TryParse(maxLengthSetting, out this._maxLength);

            string maxDurationSetting = ConfigurationManager.AppSettings["TapMaxDuration"];
            this._maxDuration = 200;
            if (!string.IsNullOrEmpty(maxDurationSetting))
                double.TryParse(maxDurationSetting, out this._maxDuration);
        }

        #endregion

        #region Fonction protégées

        /// <summary>
        /// Se produit sur l'attachement entre l'instance et le behavior
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            if (this.AssociatedObject != null)
                this.BindToTap();
        }

        /// <summary>
        /// Se produit sur le détachement entre l'instance et le behavior
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (this.AssociatedObject != null)
                this.UnbindFromTap();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Execute la commande spécifiée.
        /// </summary>
        private void ExecuteCommand(object parameter)
        {
            bool canExecute = this.PassTouchArgs ? this.Command.CanExecute(parameter) : this.Command.CanExecute(this.CommandParameter);
            if (this.Command != null && canExecute)
            {
                if (this.PassTouchArgs)
                    this.Command.Execute(parameter);
                else
                    this.Command.Execute(this.CommandParameter);
            }
        }

        /// <summary>
        /// Se produit lorsque l'utilisateur appui sur le boutton gauche de la souris dur l'élément.
        /// </summary>
        /// <param name="sender">Source de l'appel.</param>
        /// <param name="e">Argument de l'appel.</param>
        private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!this._isTouching)
            {
                this._mouseDownTime = DateTime.Now;

                // Lance le storyboard sur le touch down

                if (this.TouchDownStoryboard != null)
                    this.TouchDownStoryboard.Begin();
            }
        }

        /// <summary>
        /// Se produit lorsque l'utilisateur relache le bouton gauche de la souris sur l'élément.
        /// </summary>
        /// <param name="sender">Source de l'appel.</param>
        /// <param name="e">Argument de l'appel.</param>
        private void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!this._isTouching)
            {
                // Lance le storyboard sur le touch up

                if (this.TouchUpStoryboard != null)
                    this.TouchUpStoryboard.Begin();

                DateTime mouseUpTime = DateTime.Now;

                // Calcul du temps de pression

                TimeSpan delta = mouseUpTime.Subtract(this._mouseDownTime);

                double miliseconds = delta.TotalMilliseconds;

                // Appel de la commande si le click à durée moins de temps que le parametrage en millisecondes.

                if (miliseconds < this._maxDuration)
                    this.ExecuteCommand(e);
            }
        }

        /// <summary>
        /// Se produit lorsque l'utilisateur presse son doigt sur l'élément.
        /// </summary>
        /// <param name="sender">Source de l'appel.</param>
        /// <param name="e">Argument de l'appel.</param>
        private void OnTouchDown(object sender, TouchEventArgs e)
        {
            this._startTouch = e.GetTouchPoint(App.Current.MainWindow);

            this._touchDownTime = DateTime.Now;

            // Sert à empecher l'événement de se produire aussi à la souris

            this._isTouching = true;

            // Lance le storyboard sur le touch down

            if (this.TouchDownStoryboard != null)
                this.TouchDownStoryboard.Begin();
        }

        /// <summary>
        /// Se produit lorsque l'utilisateur sort son doigt de l'élément.
        /// Annule l'événement tap.
        /// </summary>
        /// <param name="sender">Source de l'appel.</param>
        /// <param name="e">Argument de l'appel.</param>
        private void OnTouchLeave(object sender, TouchEventArgs e)
        {
            // Annulation du touch courant.

            this._isTouching = false;
            this._touchDownTime = DateTime.MinValue;

            // Lance le storyboard sur le touch up

            if (this.TouchUpStoryboard != null)
                this.TouchUpStoryboard.Begin();
        }

        /// <summary>
        /// Se produit lorsque l'utilisateur lève son doigt de l'élément.
        /// </summary>
        /// <param name="sender">Source de l'appel.</param>
        /// <param name="e">Argument de l'appel.</param>
        private void OnTouchUp(object sender, TouchEventArgs e)
        {
            // Lance le storyboard sur le touch up

            if (this.TouchUpStoryboard != null)
                this.TouchUpStoryboard.Begin();

            DateTime touchUpTime = DateTime.Now;

            // Calcul du temps de pression

            TimeSpan delta = touchUpTime.Subtract(this._touchDownTime);

            double miliseconds = delta.TotalMilliseconds;

            // Calcul de la distance de pression

            TouchPoint endTouch = e.GetTouchPoint(App.Current.MainWindow);

            Point start = new Point(this._startTouch.Position.X, this._startTouch.Position.Y);
            Point end = new Point(endTouch.Position.X, endTouch.Position.Y);

            Vector dist = start - end;

            // Appel de la commande si la pression à durée moins de temps que le parametrage en millisecondes.

            if (miliseconds <= this._maxDuration && dist.Length <= this._maxLength)
                this.ExecuteCommand(e);

            this._isTouching = false;
        }

        /// <summary>
        /// Attache la référence aux événements de touch
        /// </summary>
        private void BindToTap()
        {
            this.AssociatedObject.IsManipulationEnabled = true;
            this.AssociatedObject.TouchDown += this.OnTouchDown;
            this.AssociatedObject.TouchUp += this.OnTouchUp;
            this.AssociatedObject.TouchLeave += this.OnTouchLeave;

            this.AssociatedObject.MouseDown += this.OnMouseDown;
            this.AssociatedObject.MouseUp += this.OnMouseUp;
        }

        /// <summary>
        /// Détache la référence des événements de touch
        /// </summary>
        private void UnbindFromTap()
        {
            this.AssociatedObject.TouchDown -= this.OnTouchDown;
            this.AssociatedObject.TouchUp -= this.OnTouchUp;
            this.AssociatedObject.TouchLeave -= this.OnTouchLeave;

            this.AssociatedObject.MouseDown -= this.OnMouseDown;
            this.AssociatedObject.MouseUp -= this.OnMouseUp;
        }

        #endregion
    }
}
